package com.example.perftool2;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.BatteryStats;
import android.os.BatteryStatsManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.UidBatteryConsumer;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class Tool {

    private static final String TAG = "perf-tool";

    private static final String THERMAL_PATH = "/sys/class/thermal/thermal_zone45/temp";
    private static final long DUMP_INTERVAL = 2000L;
    private Context mContext;
    private final ActivityManager mActivityManager;
    private final PackageManager mPm;
    private final BatteryStatsManager mBatteryStats;
    private MemTool mMemTool;
    private String mTotalFreeMem;
//    private CpuTool mCpuTool;
    private DumpsysCpuInfo mDumpsysCpuInfo;
    private String mGpuPercentage = "no_init";
    private String mIdleCpu;
    private float mTemperature;
    private boolean isStarted;
    private StateChangeListener mStateChangeListener;

    private Handler mDumpHandler;
    private Handler mWriteHandler;
    private Handler mMainHandler;
    private HandlerThread mDumpThread, mWriteThread;

    private FileWriter mFileWriter;
    private String[] mDumpApp;
    public Tool(Context context) {
        mContext = context;
        mActivityManager = context.getSystemService(ActivityManager.class);
        mPm = context.getPackageManager();
        mBatteryStats = context.getSystemService(BatteryStatsManager.class);
        mMemTool = new MemTool();
        mDumpsysCpuInfo = new DumpsysCpuInfo();

        mMainHandler = new Handler(Looper.getMainLooper());

        mDumpThread = new HandlerThread("perf-tool-dump");
        mDumpThread.start();

        mDumpHandler = new DumpHandler(mDumpThread.getLooper());

        mWriteThread = new HandlerThread("perf-tool-write");
        mWriteThread.start();
        mWriteHandler = new WriteHandler(mWriteThread.getLooper());

        mFileWriter = new FileWriter(context);
        mFileWriter.initFile();

        mDumpApp = SettingsUtils.getPerfApps(mContext);
        // set settings pid
        getRunningApps();
    }

    private static final int DUMP_MSG_START = 1;
    private static final int DUMP_MSG_STOP = 2;

    class DumpHandler extends Handler {

        public DumpHandler(@NonNull Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case DUMP_MSG_START:
                    startImpl();
                    break;
                case DUMP_MSG_STOP:
                    stopImpl();
                    break;
            }
        }
    }

    private static final int WRITE_MSG_SAVE = 1;

    class WriteHandler extends Handler {
        public WriteHandler(@NonNull Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what) {
                case WRITE_MSG_SAVE:
                    mFileWriter.save(true, (String)msg.obj);
                    break;
            }
        }
    }

    public boolean isStarted() {
        return isStarted;
    }

    public void setStateChangeListener(StateChangeListener mStateChangeListener) {
        this.mStateChangeListener = mStateChangeListener;
    }

    private void startImpl() {
        Log.i(TAG, "^^^^ start dump ^^^^");
        ArrayList<AppInfo> runningApps = getRunningApps();
        getNetworkData(runningApps);
        getBatteryStats(runningApps);

        getMemData(runningApps);
        getAppCpuData(runningApps);
        getGpuData();
        getTempData();
        getIdleCpu();

        printData(runningApps);
        Log.i(TAG, "^^^^ end dump ^^^^");

        mDumpHandler.sendEmptyMessageDelayed(DUMP_MSG_START, DUMP_INTERVAL);
    }

    private void stopImpl() {
        mDumpHandler.removeMessages(DUMP_MSG_START);
    }

    public void startDump() {
        if (isStarted) return;
        isStarted = true;
        applyStateChange();
        mDumpHandler.sendEmptyMessage(DUMP_MSG_START);
    }

    public void stopDump() {
        if (!isStarted) return;
        isStarted = false;
        applyStateChange();
        mDumpHandler.sendEmptyMessage(DUMP_MSG_STOP);
    }

    public void quit() {
        if (mDumpThread.isAlive()) mDumpThread.quitSafely();
        if (mWriteThread.isAlive()) mWriteThread.quitSafely();
    }

    private void getMemData(ArrayList<AppInfo> runningApps) {
        int[] pids = getPids(runningApps);
        long[] memoryUsage = mMemTool.getMemoryUsage(mActivityManager, pids);
        if (memoryUsage.length != pids.length) return;
        for (int i = 0; i < pids.length; i++) {
            int p = pids[i];
            long m = memoryUsage[i];
            for (AppInfo app : runningApps) {
                if (app.getPid() == p) {
                    app.setMem(m);
                    app.setMemPercent(SettingsUtils.getMemPercent(mContext, p));
                    break;
                }
            }
        }
        mTotalFreeMem = mMemTool.getTotalFreeMem();
    }

    private void getTempData() {
        ShellUtils.CommandResult commandResult =
                ShellUtils.execCommand("cat " + THERMAL_PATH, false);
        Log.i(TAG, "getTempData, result:" + commandResult.result);
        Log.i(TAG, "getTempData, successMsg:" + commandResult.successMsg);
        Log.i(TAG, "getTempData, errorMsg:" + commandResult.errorMsg);
        if (commandResult.result == 0) {
            mTemperature = Integer.parseInt(commandResult.successMsg) / 1000.0f;
        }
    }

    private void getIdleCpu() {
        mIdleCpu = SettingsUtils.getIdleCpu(mContext);
    }

    private int[] getPids(ArrayList<AppInfo> apps) {
        ArrayList<Integer> pidList = new ArrayList<>();
        for (AppInfo e: apps){
            pidList.add(e.getPid());
        }
        int[] ret = new int[pidList.size()];
        for (int i = 0; i < pidList.size(); i++) {
            ret[i] = pidList.get(i);
        }
        return ret;
    }

    private void printData(ArrayList<AppInfo> runningApps) {
        Log.i(TAG, "printData, start ##############################");
        for (AppInfo appInfo : runningApps) {
            Log.i(TAG, "printData, appinfo:" + appInfo);
        }
        Log.i(TAG, "printData, mGpuPercentage:" + mGpuPercentage);
        Log.i(TAG, "printData, mTemperature:" + mTemperature);
        Log.i(TAG, "printData, mIdleCpu:" + mIdleCpu);
        Log.i(TAG, "printData, mTotalFreeMem:" + mTotalFreeMem);

        Message msg = mWriteHandler.obtainMessage(WRITE_MSG_SAVE, constructCsvData(runningApps));
        mWriteHandler.sendMessage(msg);

        Log.i(TAG, "printData, end ##############################");
    }

    /**
     * "时间",
     * "pid",
     * "uid",
     * "名称",
     * "CPU(cpuinfo)",
     * "CPU(top)",
     * "CPU_idle",
     * "GPU",
     * "Mem(meminfo)",
     * "Mem%",
     * "Total Free Mem",
     * "温度",
     * "耗电",
     * "下载",
     * "上传"
     */
    private String constructCsvData(ArrayList<AppInfo> runningApps) {
        StringBuilder sb = new StringBuilder();
        String time = FileWriter.getTimeStamp(true);
        for (AppInfo appInfo : runningApps) {
            // 时间
            sb.append(time);
            sb.append(FileWriter.COMMA);
            // pid
            sb.append(appInfo.getPid());
            sb.append(FileWriter.COMMA);
            // uid
            sb.append(appInfo.getUid());
            sb.append(FileWriter.COMMA);
            // 名称
            sb.append(appInfo.getName());
            sb.append(FileWriter.COMMA);
            // CPU(cpuinfo)
            sb.append(appInfo.getDumpsysCpuUsage());
            sb.append(FileWriter.COMMA);
            // CPU(top)
            sb.append(appInfo.getShCpuUsage());
            sb.append(FileWriter.COMMA);
            // CPU_idle
            sb.append(mIdleCpu);
            sb.append(FileWriter.COMMA);
            // GPU
            sb.append(mGpuPercentage);
            sb.append(FileWriter.COMMA);
            // Mem
            sb.append(appInfo.getMemString());
            sb.append(FileWriter.COMMA);
            // Mem%
            sb.append(appInfo.getMemPercent());
            sb.append("%");
            sb.append(FileWriter.COMMA);
            // Total Free Mem
            sb.append(mTotalFreeMem);
            sb.append(FileWriter.COMMA);
            // 温度
            sb.append(mTemperature);
            sb.append(FileWriter.COMMA);
            // 耗电
            sb.append(appInfo.getPowerConsume());
            sb.append(FileWriter.COMMA);
            // 下载
            sb.append(appInfo.getRxString());
            sb.append(FileWriter.COMMA);
            // 上传
            sb.append(appInfo.getTxString());
            sb.append('\n');
        }
        return sb.toString();
    }

    private void getAppCpuData(ArrayList<AppInfo> runningApps) {
        mDumpsysCpuInfo.getData(runningApps);
        for (AppInfo appInfo : runningApps) {
            int pid = appInfo.getPid();
            if (pid != 0) {
                appInfo.setShCpuUsage(SettingsUtils.getCpu(mContext, pid));
            }
        }
    }


    private void getGpuData () {
        ShellUtils.CommandResult commandResult = ShellUtils.execCommand("cat /sys/class/kgsl/kgsl-3d0/gpu_busy_percentage", false);
        Log.i(TAG, "getGpuData, result:" + commandResult.result);
        Log.i(TAG, "getGpuData, successMsg:" + commandResult.successMsg);
        Log.i(TAG, "getGpuData, errorMsg:" + commandResult.errorMsg);
        if (commandResult.result == 0) {
            mGpuPercentage = commandResult.successMsg;
        }
    }

    public void getNetworkData(ArrayList<AppInfo> runningApps) {
        for (AppInfo app: runningApps) {
            app.updateNetworkDate();
        }
    }

    public void getBatteryStats(ArrayList<AppInfo> runningApps) {
        List<UidBatteryConsumer> consumerList = mBatteryStats.getBatteryUsageStats().getUidBatteryConsumers();
        for (UidBatteryConsumer consumer : consumerList) {
            for (AppInfo appInfo : runningApps) {
                if (appInfo.getUid() == consumer.getUid()) {
                    appInfo.setPowerConsume(formatValue(consumer.getConsumedPower()));
                    break;
                }
            }
        }
    }

    private ArrayList<AppInfo> getRunningApps() {
        ArrayList<AppInfo> apps = new ArrayList<>();
        List<ActivityManager.RunningAppProcessInfo> runs = mActivityManager.getRunningAppProcesses();
        for (String pkgName: mDumpApp) {
            AppInfo ai = new AppInfo();
            ai.setPkgName(pkgName);
            ai.setName(getAppName(pkgName));
            ai.setUid(getUidByPackageName(pkgName));
            for(ActivityManager.RunningAppProcessInfo info : runs) {
                if (TextUtils.equals(pkgName, info.pkgList[0])) {
                    ai.setPid(info.pid);
                    break;
                }
            }
            if (ai.getPid() == 0 && TextUtils.equals("system_server", ai.getPkgName())) {
                ai.setPid(getPidByPsCommand());
            }
            Log.i(TAG, "getRunningApps, add app info:" + ai);
            apps.add(ai);
        }
        return apps;
    }

    private int getPidByPsCommand() {
        String s = Settings.Global.getString(mContext.getContentResolver(), "pid_of_ss");
        if(!TextUtils.isEmpty(s)) {
            return Integer.parseInt(s);
        }
        return 0;
    }


    private int getUidByPackageName(String pkgName) {
        try {
            ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkgName, 0);
            return applicationInfo.uid;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    private static String formatValue(double value) {
        if (value == 0) return "0";

        final String format;
        if (value < .00001) {
            format = "%.8f";
        } else if (value < .0001) {
            format = "%.7f";
        } else if (value < .001) {
            format = "%.6f";
        } else if (value < .01) {
            format = "%.5f";
        } else if (value < .1) {
            format = "%.4f";
        } else if (value < 1) {
            format = "%.3f";
        } else if (value < 10) {
            format = "%.2f";
        } else if (value < 100) {
            format = "%.1f";
        } else {
            format = "%.0f";
        }

        // Use English locale because this is never used in UI (only in checkin and dump).
        return String.format(Locale.ENGLISH, format, value);
    }

    public static String byteToMB(long size) {
//        Log.i(TAG, "byteToMB, size:" + size);
        long kb = 1024;
        long mb = kb * 1024;
        long gb = mb * 1024;
        if (size >= gb) {
            return String.format("%.6f GB", size / (1.0f * gb));
        } else if (size >= mb) {
            float f = size / (1.0f * mb);
            return String.format("%.3f MB", f);
        } else if (size > kb) {
            float f = (float) size / kb;
            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
        } else {
            return String.format("%d B", size);
        }
    }

    private void applyStateChange() {
        if (mStateChangeListener != null) mStateChangeListener.onChange(isStarted);
    }

    public interface StateChangeListener {
        void onChange(boolean isStarted);
    }


    public String getAppName(String pkgName) {
        try {
            PackageInfo packageInfo = mPm.getPackageInfo(pkgName, 0);
            return packageInfo.applicationInfo.loadLabel(mPm).toString();
        } catch (Throwable e) {
            Log.i(TAG,"getAppName >> e:" + e.toString());
        }
        return pkgName;
    }
}
